package org.fjsei.yewu.graphql;

import graphql.TrivialDataFetcher;
import graphql.relay.*;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static graphql.Assert.assertNotNull;
import static graphql.Assert.assertTrue;
import static java.lang.String.format;
import static java.util.Base64.getDecoder;
import static java.util.Base64.getEncoder;

//都是private的无法直接利用旧代码extends SimpleListConnection<T> 只好抄了；
/**Relay包装器功能
    内存分页版本，需要把全部数据装载到服务端。限制集合太大的就不要使用该版本。请改用DbPageConnection；
 这个分页器： 必须一次性把所有的数据items[]全部查询出来的。
 * */
public class MemoryListConnection<T> implements DataFetcher<Connection<T>>, TrivialDataFetcher<Connection<T>> {
    static final String DUMMY_CURSOR_PREFIX = "cursor";
    protected  String prefix;
    protected  List<T> data;
    //单页面查询实际限制数
    protected int limit;

    /**限制后端服务器上内存集合的大小，避免从数据库查询太多的数据集Set<>。
     * maxLen代表data队列的允许最大大小。
     * maxLimit是允许当一次查询返回给前端的最多条数。
     * */
    public MemoryListConnection(List<T> data,int maxLen, String prefix,int maxLimit) {
        this.data = assertNotNull(data, () -> " data cannot be null");
        assertTrue(data.size() <= maxLen, () -> "集合太大了");
        assertTrue(prefix != null && !prefix.isEmpty(), () -> "prefix cannot be null or empty");
        this.prefix = prefix;
        this.limit=maxLimit;
    }
    public MemoryListConnection() {
    }
    public MemoryListConnection(List<T> data,int maxLen, String prefix) {
        this(data,maxLen,prefix,10);
    }
    public MemoryListConnection(List<T> data,int maxLen) {
        this(data,maxLen,DUMMY_CURSOR_PREFIX,10);
    }
    //缺省最大队列 1000条。
    public MemoryListConnection(List<T> data) {
        this(data,1000,DUMMY_CURSOR_PREFIX,10);
    }

    public int getLimit() {
        return limit;
    }

    /**假如前后两次数据队列｛顺序，过滤后｝没有发生变动，就没必要使用setListData*/
    public MemoryListConnection<T> setListData(List<T> data) {
        this.data= assertNotNull(data, () -> " data cannot be null");
        return this;
    }
    //必须实现get接口！
    //限制返回条数; data是已经过滤和排队后的；
    @Override
    public Connection<T> get(DataFetchingEnvironment environment) {
        //全部数据都是一次性给data的。那么wholeOffset=0；数据库分页每次只是取到单页面数据才需要设置初始位置。
        List<Edge<T>> edges = buildEdges(0);

        if (edges.size() == 0) {
            return emptyConnection();
        }

        ConnectionCursor firstPresliceCursor = edges.get(0).getCursor();
        ConnectionCursor lastPresliceCursor = edges.get(edges.size() - 1).getCursor();

        int afterOffset = getOffsetFromCursor(environment.getArgument("after"), -1);
        int begin = Math.max(afterOffset, -1) + 1;
        int beforeOffset = getOffsetFromCursor(environment.getArgument("before"), edges.size());
        int end = Math.min(beforeOffset, edges.size());

        if (begin > end) begin = end;

        edges = edges.subList(begin, end);
        if (edges.size() == 0) {
            return emptyConnection();
        }

        Integer first = environment.getArgument("first");
        Integer last = environment.getArgument("last");

        if (first != null) {
            if (first < 0) {
                throw new InvalidException(format("The page size must not be negative: 'first'=%s", first));
            }
            if(first>limit)   first=limit;
            edges = edges.subList(0, first <= edges.size() ? first : edges.size());
        }
        if (last != null) {
            if (last < 0) {
                throw new InvalidException(format("The page size must not be negative: 'last'=%s", last));
            }
            if(last>limit)   last=limit;
            edges = edges.subList(last > edges.size() ? 0 : edges.size() - last, edges.size());
        }

        if (edges.isEmpty()) {
            return emptyConnection();
        }

        Edge<T> firstEdge = edges.get(0);
        Edge<T> lastEdge = edges.get(edges.size() - 1);

        PageInfo pageInfo = new DefaultPageInfo(
                firstEdge.getCursor(),
                lastEdge.getCursor(),
                !firstEdge.getCursor().equals(firstPresliceCursor),
                !lastEdge.getCursor().equals(lastPresliceCursor)
        );

        return new DefaultConnection<>(
                edges,
                pageInfo
        );
    }
    /**假如过滤条件变化了，或者排序字段方向任何一个变动，都将会导致旧的Cursor索引号失效。
     * 前端必须注意，已读取缓存的集合前端需要全部清除才行。
     * 放在数据库分页的：Cursor计算也必须从偏移量起头wholeOffset计算的；
     * */
    protected List<Edge<T>> buildEdges(int wholeOffset) {
        List<Edge<T>> edges = new ArrayList<>();
        //数据库分页每次只是取到单页面数据才需要设置wholeOffset初始位置。
        int ix = wholeOffset;
        for (T object : data) {
            edges.add(new DefaultEdge<>(object, new DefaultConnectionCursor(createCursor(ix++))));
        }
        return edges;
    }

    protected Connection<T> emptyConnection() {
        PageInfo pageInfo = new DefaultPageInfo(null, null, false, false);
        return new DefaultConnection<>(Collections.emptyList(), pageInfo);
    }

    /**
     * find the object's cursor, or null if the object is not in this connection.
     *
     * @param object the object in play
     *
     * @return a connection cursor
     * 没用？
     */
    public ConnectionCursor cursorForObjectInConnection(T object) {
        int index = data.indexOf(object);
        if (index == -1) {
            return null;
        }
        String cursor = createCursor(index);
        return new DefaultConnectionCursor(cursor);
    }

    protected int getOffsetFromCursor(String cursor, int defaultValue) {
        if (cursor == null) {
            return defaultValue;
        }
        byte[] decode;
        try {
            decode = getDecoder().decode(cursor);
        } catch (IllegalArgumentException e) {
            throw new InvalidException(format("The cursor is not in base64 format : '%s'", cursor), e);
        }
        String string = new String(decode, StandardCharsets.UTF_8);
        if (prefix.length() > string.length()) {
            throw new InvalidException(format("The cursor prefix is missing from the cursor : '%s'", cursor));
        }
        try {
            return Integer.parseInt(string.substring(prefix.length()));
        } catch (NumberFormatException nfe) {
            throw new InvalidException(format("The cursor was not created by this class  : '%s'", cursor), nfe);
        }
    }

    protected String createCursor(int offset) {
        byte[] bytes = (prefix + Integer.toString(offset)).getBytes(StandardCharsets.UTF_8);
        return getEncoder().encodeToString(bytes);
    }
}

